#include "stdafx.h"

#include "MxUtils\MxError1.h"
#include "PublicInterfaces\{FAFD3CB8-FCF3-46EE-B32E-45A4FF68073E}\IImageComputeFn.h"
#include "..\..\PublicInterfaces\PluginICPopenCV\PluginICPopenCVErrorCodes.h"
#include "PluginICPopenCV.h"

#include <opencv2\core\core.hpp>
#include <opencv2\legacy\legacy.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>	//needed for imread(), imwrite()

#include "Utils.h"
#include "SnakeImage.h"


SnakeImage::SnakeImage(void):
	_refCnt(0)
{
}


SnakeImage::~SnakeImage(void)
{
}

bool		MXSTDMETHOD SnakeImage::Invoke(IImageComputeFn *fn, bool DebugMode, bool DisplayMsg)
{
	bool rc = false; 

	if ((fn == nullptr) || (fn->IsValid() == false))
		MX_SETERROR(MX11146, MxError::InvalidParams,  MxError::Abort,  MxError::VerboseReport, "fn is nullptr, or invalid");
	else
	{
		if (   (fn->IsExistParam("StopProc", IImageComputeFnDef::ParamType::Bool, IImageComputeFnDef::Direction::IO) == false)
			|| (fn->IsExistParam("RootFolder", IImageComputeFnDef::ParamType::String, IImageComputeFnDef::Direction::Input) == false)
			|| (fn->IsExistParam("IJC", IImageComputeFnDef::ParamType::String, IImageComputeFnDef::Direction::Input) == false)
			|| (fn->IsExistParam("IJF", IImageComputeFnDef::ParamType::String, IImageComputeFnDef::Direction::Input) == false)
			|| (fn->IsExistParam("ImageRef", IImageComputeFnDef::ParamType::String, IImageComputeFnDef::Direction::IO) == false)
			|| (fn->IsExistParam("Alpha", IImageComputeFnDef::ParamType::Double, IImageComputeFnDef::Direction::Input) == false)
			|| (fn->IsExistParam("Beta", IImageComputeFnDef::ParamType::Double, IImageComputeFnDef::Direction::Input) == false)			
			|| (fn->IsExistParam("Gamma", IImageComputeFnDef::ParamType::Double, IImageComputeFnDef::Direction::Input) == false)				
			|| (fn->IsExistParam("NeighborHt", IImageComputeFnDef::ParamType::Integer, IImageComputeFnDef::Direction::Input) == false)	
			|| (fn->IsExistParam("NeighborWd", IImageComputeFnDef::ParamType::Integer, IImageComputeFnDef::Direction::Input) == false)	
			|| (fn->IsExistParam("MaxIter", IImageComputeFnDef::ParamType::Integer, IImageComputeFnDef::Direction::Input) == false)	
			|| (fn->IsExistParam("Epsilon", IImageComputeFnDef::ParamType::Double, IImageComputeFnDef::Direction::Input) == false)	
			|| (fn->IsExistParam("InitPts", IImageComputeFnDef::ParamType::String, IImageComputeFnDef::Direction::Input) == false)
			|| (fn->IsExistParam("Gradient", IImageComputeFnDef::ParamType::Integer, IImageComputeFnDef::Direction::Input) == false)
			|| (fn->IsExistParam("ResPts", IImageComputeFnDef::ParamType::String, IImageComputeFnDef::Direction::Output) == false)
			|| (fn->IsExistParam("RotRect", IImageComputeFnDef::ParamType::String, IImageComputeFnDef::Direction::Output) == false)
			|| (fn->IsExistParam("Score", IImageComputeFnDef::ParamType::Double, IImageComputeFnDef::Direction::Output) == false)	
			)
			MX_SETERROR(MX11148, MxError::BadUserInput,  MxError::Abort,  MxError::VerboseReport, "IJCFolder, ImageFolder, ImageRef, Alpha, Beta, Gamma, NeighborHt, NeighborWd, MaxIter, Epsilon, ArrayIntsPoints, Gradient, Score  are not supported by this fn");
		else
		{
			if (fn->GetParamBool("StopProc") == true)
			{
				rc = true;
			}
			else
			{
				double    score = -1.0;
				std::string resultPts("ArrayInts:[empty]");
				std::string rotRect("ArrayInts:[empty]");
				std::string initPts = fn->GetParamStr("InitPts");

				bool done = false;
				if (initPts == "ArrayInts:[empty]")
					done = true;
				else
				{
					std::string imageRefDst("");
					std::string imageRef(fn->GetParamStr("ImageRef"));
					std::string pathfilenameSrcImage("");
					std::string pathfilenameDstImage("");
					std::string pathfilenameDebugImage("");

					if (PluginOpenCV::Utils::GetImagePathFilenames(fn, fn->GetParamStr("RootFolder"), fn->GetParamStr("IJC"), fn->GetParamStr("IJF"), imageRef.c_str(),
						fn->GetFileType(), fn->GetFileType(), //if function changes the filetype (.bmp to .jpg) then add to function params the std IO variable ImageProcFilev1::VariableFileTypeName and set it to new filetype before returning - see Job::Run()
						&imageRefDst, &pathfilenameSrcImage, &pathfilenameDstImage, &pathfilenameDebugImage) == false)
						MX_SETERROR(MX11196, MxError::CodeDefect, MxError::Abort, MxError::VerboseReport, "GetImagePathFilenames failed for step=%d", fn->GetSeqNo());
					else
					{
						CvPoint  *points = nullptr;
						int       pointCnt = 0;

						if (FindBoundary(fn, pathfilenameSrcImage.c_str(), (DebugMode == true) ? pathfilenameDebugImage.c_str() : nullptr,
							fn->GetParamDouble("Alpha"), fn->GetParamDouble("Beta"), fn->GetParamDouble("Gamma"),
							fn->GetParamInt("NeighborHt"), fn->GetParamInt("NeighborWd"),
							fn->GetParamInt("MaxIter"), fn->GetParamDouble("Epsilon"),
							fn->GetParamStr("InitPts"), fn->GetParamInt("Gradient"), 
							&resultPts, &rotRect, &score) == false)
							MX_SETERROR(MX11219, MxError::CodeDefect, MxError::Abort, MxError::VerboseReport, "FindBoundary() failed");
						else
						{
							done = true;
						}
					}
				}
				if (done == true)
				{
					if (fn->SetParamStr("ResPts", resultPts.c_str()) == false)
						MX_SETERROR(MX11234, MxError::CodeDefect, MxError::Abort, MxError::VerboseReport, "SetParamStr(ResPts) failed");
					else
					{
						if (fn->SetParamStr("RotRect", rotRect.c_str()) == false)
							MX_SETERROR(MX11243, MxError::CodeDefect, MxError::Abort, MxError::VerboseReport, "SetParamStr(RotRect) failed");
						else
						{
							if (fn->SetParamDouble("Score", score) == false)
								MX_SETERROR(MX11235, MxError::CodeDefect, MxError::Abort, MxError::VerboseReport, "SetParamStr(Score) failed");
							else
							{
								rc = true;
							}
						}
					}
				}
			}
		}
	}
	return rc;
}

bool		MXSTDMETHOD SnakeImage::FindBoundary(IImageComputeFn *fn, const char *srcImagePathFilename, const char *destImagePathFilename, double alpha, double beta, double gamma, int neighborhoodSizeHt, int neighborhoodSizeWd, int maxIter, double epsilon, const char *initPts, int gradient, std::string *resultPts, std::string *rotRect, double *score)
{
	bool rc = false;

	if ((srcImagePathFilename == nullptr) || (initPts == nullptr) || (resultPts == nullptr) || (rotRect == nullptr) || (score == nullptr) || (fn == nullptr) )
		MX_SETERROR(MX11152, MxError::InvalidParams,  MxError::Abort,  MxError::QuietReport, "one or more parameters are invalid");
	else
	{
		CvPoint  *points =  nullptr;
		int		 *arrayInt = nullptr;
		char	 *buffRes = nullptr;
		char	 *buffRect = nullptr;

		try
		{
			int arrayCnt = 0;
			if (((arrayCnt = fn->ArrayIntsGetSize(initPts)) == ArrayInts::Invalid) || (arrayCnt % 2))
				MX_SETERROR(MX11153, MxError::BadUserInput, MxError::Abort, MxError::VerboseReport, "ArrayInts::GetSize(ArrayIntPoints) failed or returned odd number %d", arrayCnt);
			else
			{
				arrayInt = new int[arrayCnt];
				if (arrayInt == nullptr)
					MX_SETERROR(MX11154, MxError::System,  MxError::Abort,  MxError::QuietReport, "arrayInt == nullptr - out of memory?");
				else
				{
					if (fn->ArrayIntsConvertStr2Ints(initPts, arrayInt, arrayCnt, IImageComputeFn::Invalid) == false)
						MX_SETERROR(MX11155, MxError::CodeDefect,  MxError::Abort,  MxError::QuietReport, "ArrayInts::ConvertStr2Ints() failed");
					else
					{
						int pointCnt = arrayCnt / 2;
						std::vector<cv::Point> pts(pointCnt);
		
						points = new CvPoint[pointCnt];
						if (points == nullptr)
							MX_SETERROR(MX11156, MxError::System,  MxError::Abort,  MxError::QuietReport, "arrayInt == nullptr - out of memory?");
						else
						{
							int y = 0;
							for (int x = 0; x < pointCnt; x++ )
							{
								points[x].x = arrayInt[y++];
								points[x].y = arrayInt[y++];
								pts[x] = points[x];
							}

							cv::RotatedRect rectInit = cv::fitEllipse(pts);//calc bounding rect for initial points

							float falpha = (float)alpha;	// Weight of continuity energy
							float fbeta = (float)beta;		// Weight of curvature energy
							float fgamma = (float)gamma;	// Weight of image energy

							CvSize size; // Size of neighborhood of every point used to search the minimumm have to be odd
							size.width = neighborhoodSizeWd;
							size.height = neighborhoodSizeHt;

							CvTermCriteria criteria;
							criteria.type = CV_TERMCRIT_ITER;  // terminate processing after X iteration
							criteria.max_iter = maxIter; 
							criteria.epsilon = epsilon;

							cv::Ptr<IplImage>img = cvLoadImage(srcImagePathFilename, 0);
							if (img == nullptr)
								MX_SETERROR(MX11157, MxError::BadUserInput,  MxError::Abort,  MxError::VerboseReport, "unable to open source image=%s", srcImagePathFilename);
							else
							{
								cvSnakeImage(img, points, pointCnt, &falpha, &fbeta, &fgamma, CV_VALUE, size, criteria, gradient );

								int y = 0;
								for (int x = 0; x < pointCnt; x++)
								{
									pts[x] = points[x];
									arrayInt[y++] = points[x].x;
									arrayInt[y++] = points[x].y;
								}

								cv::RotatedRect rectRes = cv::fitEllipse(pts);

								if (rectRes.size.area() == 0.0)
									*score = (double) IImageComputeFn::Invalid;
								else
									*score = 100.0 - (abs(rectInit.size.area() - rectRes.size.area()) / rectRes.size.area());

								int len = 0;
								if (((len = fn->ArrayIntsGetStrLength(arrayInt, arrayCnt, IImageComputeFn::Invalid)) == IImageComputeFn::Invalid) || ((buffRes = new char[len + 1]) == nullptr))
									MX_SETERROR(MX11158, MxError::CodeDefect, MxError::Abort, MxError::QuietReport, "ArrayIntsGetStrLength() failed or out of memory - resultPts");
								else
								{
									if (fn->ArrayIntsConvertInts2Str(arrayInt, arrayCnt, buffRes, len, IImageComputeFn::Invalid) == nullptr)
										MX_SETERROR(MX11236, MxError::CodeDefect, MxError::Abort, MxError::QuietReport, "ArrayIntsConvertInts2Str() failed - resultPts");
									else
									{
										resultPts->assign(buffRes);

										int arrayRect[IImageComputeFn::ArrayIntsRotRectSize];

										arrayRect[IImageComputeFn::ArrayIntsRotRectOffsetCentreX] = static_cast<int>(rectRes.center.x);
										arrayRect[IImageComputeFn::ArrayIntsRotRectOffsetCentreY] = static_cast<int>(rectRes.center.y);
										arrayRect[IImageComputeFn::ArrayIntsRotRectOffsetWidth] = static_cast<int>(rectRes.size.width);
										arrayRect[IImageComputeFn::ArrayIntsRotRectOffsetHeight] = static_cast<int>(rectRes.size.height);
										arrayRect[IImageComputeFn::ArrayIntsRotRectOffsetAngle] = static_cast<int>(rectRes.angle);

										if (((len = fn->ArrayIntsGetStrLength(arrayRect, IImageComputeFn::ArrayIntsRotRectSize, IImageComputeFn::Invalid)) == IImageComputeFn::Invalid) || ((buffRect = new char[len + 1]) == nullptr))
											MX_SETERROR(MX11244, MxError::CodeDefect, MxError::Abort, MxError::QuietReport, "ArrayIntsGetStrLength() failed or out of memory - buffRect");
										else
										{
											if (fn->ArrayIntsConvertInts2Str(arrayRect, IImageComputeFn::ArrayIntsRotRectSize, buffRect, len, IImageComputeFn::Invalid) == nullptr)
												MX_SETERROR(MX11245, MxError::CodeDefect, MxError::Abort, MxError::QuietReport, "ArrayIntsConvertInts2Str() failed - buffRect");
											else
											{
												rotRect->assign(buffRect);

												if (destImagePathFilename == nullptr)
													rc = true;
												else
												{
													cv::Mat out = cv::imread(srcImagePathFilename, CV_LOAD_IMAGE_ANYDEPTH);

													cv::Point2f rectPts[4];
													rectInit.points(rectPts);
													cv::line(out, rectPts[0], rectPts[1], CV_RGB(0, 0, 0), 1, CV_AA, 0);
													cv::line(out, rectPts[1], rectPts[2], CV_RGB(0, 0, 0), 1, CV_AA, 0);
													cv::line(out, rectPts[2], rectPts[3], CV_RGB(0, 0, 0), 1, CV_AA, 0);
													cv::line(out, rectPts[3], rectPts[0], CV_RGB(0, 0, 0), 1, CV_AA, 0);

													rectRes.points(rectPts);
													cv::line(out, rectPts[0], rectPts[1], CV_RGB(0, 0, 0), 2, CV_AA, 0);
													cv::line(out, rectPts[1], rectPts[2], CV_RGB(0, 0, 0), 2, CV_AA, 0);
													cv::line(out, rectPts[2], rectPts[3], CV_RGB(0, 0, 0), 2, CV_AA, 0);
													cv::line(out, rectPts[3], rectPts[0], CV_RGB(0, 0, 0), 2, CV_AA, 0);

													for (int i = 0; i < pointCnt; i++)
													{
														int j = (i + 1) % pointCnt;
														cv::line(out, points[i], points[j], CV_RGB(0, 0, 0), 1, 8, 0);
													}
													if (cv::imwrite(destImagePathFilename, out) == false)
														MX_SETERROR(MX11237, MxError::BadUserInput, MxError::Abort, MxError::VerboseReport, "unable to write destination image=%s", destImagePathFilename);
													else
													{
														rc = true;
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		catch(std::exception& e)
		{
			std::string result("[exception msg not found]");
			MX_SETERROR(MX11238, MxError::CodeDefect, MxError::Abort, MxError::QuietReport, "threw exception %s for source image=%s", PluginICPopenCV::GetOpenCVExceptionMsg(e, &result), srcImagePathFilename);
		}
		if (points != nullptr)
			delete[] points;
		if (arrayInt != nullptr)
			delete[] arrayInt;
		if (buffRes != nullptr)
			delete[] buffRes;
		if (buffRect != nullptr)
			delete[] buffRect;
	}
	return rc;
}

long		MXSTDMETHOD SnakeImage::DestroyPtr(void)
{
	long cnt = InterlockedDecrement(&_refCnt);
	if ( cnt == 0 )
		delete this;
	return cnt;
}

long		MXSTDMETHOD SnakeImage::DuplicatePtr(void)
{
	return InterlockedIncrement(&_refCnt);
}

void *		MXSTDMETHOD SnakeImage::Dynamic_cast(const GUID IId)
{
	void *rc = nullptr;

	if ( MxGUID::IsEqual(IId, IMxPluginBaseIID::Guid()))
		rc = static_cast<IMxPluginBase *>(this);
	else
		rc = nullptr;

	std::string tmp;
	if (rc == nullptr)
		MX_SETERROR(MX11150, MxError::Install, MxError::Abort, MxError::QuietReport, "Dynamic_cast does not support IID=%s", MxGUID::ConvertToString(IId, &tmp));
	else
		((IMxPluginBase *)rc)->DuplicatePtr();

	return rc;
}


